package com.prolificinteractive.materialcalendarview;

import ohos.agp.components.AttrHelper;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentContainer;
import ohos.agp.components.DirectionalLayout;
import ohos.agp.components.Image;
import ohos.agp.components.LayoutScatter;
import ohos.agp.components.PageSlider;
import ohos.agp.components.Text;
import ohos.agp.components.element.Element;
import ohos.agp.text.Font;
import ohos.agp.utils.Color;
import ohos.app.Context;

import com.prolificinteractive.materialcalendarview.ext.Logger2;
import com.prolificinteractive.materialcalendarview.format.ArrayWeekDayFormatter;
import com.prolificinteractive.materialcalendarview.format.DayFormatter;
import com.prolificinteractive.materialcalendarview.format.MonthArrayTitleFormatter;
import com.prolificinteractive.materialcalendarview.format.TitleFormatter;
import com.prolificinteractive.materialcalendarview.format.WeekDayFormatter;

import org.threeten.bp.DayOfWeek;
import org.threeten.bp.LocalDate;
import org.threeten.bp.temporal.WeekFields;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Locale;

import static ohos.agp.components.ComponentContainer.LayoutConfig.MATCH_PARENT;

/**
 * <p>
 * This class is a calendar widget for displaying and selecting dates.
 * The range of dates supported by this calendar is configurable.
 * A user can select a date by taping on it and can page the calendar to a desired date.
 * </p>
 * <p>
 * By default, the range of dates shown is from 200 years in the past to 200 years in the future.
 * This can be extended or shortened by configuring the minimum and maximum dates.
 * </p>
 * <p>
 * When selecting a date out of range, or when the range changes so the selection becomes outside,
 * The date closest to the previous selection will become selected. This will also trigger the
 * { OnDateSelectedListener}
 * </p>
 * <p>
 * <strong>Note:</strong> if this view's size isn't divisible by 7,
 * the contents will be centered inside such that the days in the calendar are equally square.
 * For example, 600px isn't divisible by 7, so a tile size of 85 is choosen, making the calendar
 * 595px wide. The extra 5px are distributed left and right to get to 600px.
 * </p>
 */
public class MaterialCalendarView extends ComponentContainer implements Component.EstimateSizeListener,
    ComponentContainer.ArrangeListener {
    public static final int INVALID_TILE_DIMENSION = -10;
    private static final String MCV_CALENDAR_MODE = "mcv_calendarMode";
    private static final String MCV_FIRST_DAY_OF_WEEK = "mcv_firstDayOfWeek";
    private static final String MCV_TITLE_ANIMATION_ORIENTATION = "mcv_titleAnimationOrientation";
    private static final String MCV_SHOW_WEEK_DAYS = "mcv_showWeekDays";
    private static final String MCV_SELECTION_MODE = "mcv_selectionMode";
    private static final String MCV_TILE_SIZE = "mcv_tileSize";
    private static final String MCV_TILE_WIDTH = "mcv_tileWidth";
    private static final String MCV_TILE_HEIGHT = "mcv_tileHeight";
    private static final String MCV_SELECTION_COLOR = "mcv_selectionColor";
    private static final String MCV_SHOW_OTHER_DATES = "mcv_showOtherDates";
    private static final String MCV_ALLOW_CLICK_DAYS_OUTSIDE_CURRENT_MONTH = "mcv_allowClickDaysOutsideCurrentMonth";

    public enum SelectionMode {
        SELECTION_MODE_NONE(0), SELECTION_MODE_SINGLE(1),
        SELECTION_MODE_MULTIPLE(2), SELECTION_MODE_RANGE(3);
        final int value;

        SelectionMode(int value) {
            this.value = value;
        }
    }

    public enum ShowOtherDates {
        SHOW_NONE(0),
        SHOW_ALL(1 | 1 << 1 | 1 << 2),
        SHOW_DEFAULTS(1 << 2),
        SHOW_OUT_OF_RANGE(1 << 1),
        SHOW_OTHER_MONTHS(1),
        SHOW_DECORATED_DISABLED(1 << 2);

        final int value;

        ShowOtherDates(int value) {
            this.value = value;
        }
    }

    /**
     * Use this orientation to animate the title vertically
     */
    public static final int VERTICAL = 0;

    /**
     * Use this orientation to animate the title horizontally
     */
    public static final int HORIZONTAL = 1;

    /**
     * Default tile size in DIPs. This is used in cases where there is no tile size specificed and the
     * view is set to ViewGroup.LayoutParams#WRAP_CONTENT WRAP_CONTENT
     */
    public static final int DEFAULT_TILE_SIZE_DP = 44;
    private static final int DEFAULT_DAYS_IN_WEEK = 7;
    private static final int DEFAULT_MAX_WEEKS = 6;
    private static final int DAY_NAMES_ROW = 1;
    private final TitleChanger titleChanger;
    private final Text title;
    private final Image buttonPast;
    private final Image buttonFuture;
    private final CalendarPager pager;
    private CalendarPagerAdapter<?> adapter;
    private CalendarDay currentMonth;
    private DirectionalLayout topbar;
    private CalendarMode calendarMode;
    Font font;
    Font fontWeek;
    boolean isBig;
    int sizeWeek;
    int sizeDay;
    /**
     * Used for the dynamic calendar height.
     */
    private boolean mDynamicHeightEnabled;

    private final ArrayList<DayViewDecorator> dayViewDecorators = new ArrayList<>();

    private final ClickedListener onClickListener = new ClickedListener() {
        @Override
        public void onClick(Component v) {
            if (v == buttonFuture) {
                pager.setCurrentPage(pager.getCurrentPage() + 1, true);
            } else if (v == buttonPast) {
                pager.setCurrentPage(pager.getCurrentPage() - 1, true);
            }
            updateUi();
        }
    };

    private final PageSlider.PageChangedListener pageChangeListener = new PageSlider.PageChangedListener() {
        @Override
        public void onPageChosen(int position) {
            titleChanger.setPreviousMonth(currentMonth);
            currentMonth = adapter.getItem(position);
            Logger2.w("---->" + currentMonth);
            updateUi();
            dispatchOnMonthChanged(currentMonth);
        }

        @Override
        public void onPageSlideStateChanged(int state) {

        }

        @Override
        public void onPageSliding(int position, float positionOffset, int positionOffsetPixels) {
        }
    };

    private CalendarDay minDate = null;
    private CalendarDay maxDate = null;

    private OnDateSelectedListener listener;
    private OnDateLongClickListener longClickListener;
    private OnMonthChangedListener monthListener;
    private OnRangeSelectedListener rangeListener;

    CharSequence calendarContentDescription;
    private int accentColor = 0;
    private int tileHeight = INVALID_TILE_DIMENSION;
    private int tileWidth = INVALID_TILE_DIMENSION;
    private SelectionMode selectionMode = SelectionMode.SELECTION_MODE_SINGLE;
    private boolean allowClickDaysOutsideCurrentMonth = true;
    private DayOfWeek firstDayOfWeek;
    private boolean showWeekDays;

    private State state;

    private boolean oldStyle;

    public MaterialCalendarView(Context context) {
        this(context, null);
    }

    public MaterialCalendarView(Context context, AttrSet attrs) {
        super(context, attrs);
        setEstimateSizeListener(this);
        setArrangeListener(this);
        final LayoutScatter inflater = LayoutScatter.getInstance(getContext());
        final Component content = inflater.parse(ResourceTable.Layout_calendar_view, null, false);
        topbar = (DirectionalLayout) content.findComponentById(ResourceTable.Id_header);
        title = (Text) content.findComponentById(ResourceTable.Id_month_name);
        buttonPast = (Image) content.findComponentById(ResourceTable.Id_previous);
        buttonFuture = (Image) content.findComponentById(ResourceTable.Id_next);
        buttonPast.setClickedListener(onClickListener);
        buttonFuture.setClickedListener(onClickListener);
        titleChanger = new TitleChanger(title);
        pager = new CalendarPager(getContext());
        pager.addPageChangedListener(pageChangeListener);
        try {
            // <enum name="month" value="0"/>
            // <enum name="week" value="1"/>
            int calendarModeIndex = attrs.getAttr(MCV_CALENDAR_MODE).isPresent() ?
                attrs.getAttr(MCV_CALENDAR_MODE).get().getIntegerValue() : 0;
            int firstDayOfWeekInt = attrs.getAttr(MCV_FIRST_DAY_OF_WEEK).isPresent() ?
                attrs.getAttr(MCV_FIRST_DAY_OF_WEEK).get().getIntegerValue() : -1;
            titleChanger.setOrientation(
                attrs.getAttr(MCV_TITLE_ANIMATION_ORIENTATION).isPresent() ?
                    attrs.getAttr(MCV_TITLE_ANIMATION_ORIENTATION).get().getIntegerValue() : VERTICAL);
            if (firstDayOfWeekInt >= 1 && firstDayOfWeekInt <= 7) {
                firstDayOfWeek = DayOfWeek.of(firstDayOfWeekInt);
            } else {
                firstDayOfWeek = WeekFields.of(Locale.getDefault()).getFirstDayOfWeek();
            }
            showWeekDays = !attrs.getAttr(MCV_SHOW_WEEK_DAYS).isPresent() ||
                attrs.getAttr(MCV_SHOW_WEEK_DAYS).get().getBoolValue();
            newState()
                .setFirstDayOfWeek(firstDayOfWeek)
                .setCalendarDisplayMode(CalendarMode.values()[calendarModeIndex])
                .setShowWeekDays(showWeekDays)
                .commit();
            // <enum name="none" value="0"/>
            // <enum name="single" value="1"/>
            // <enum name="multiple" value="2"/>
            // <enum name="range" value="3"/>
            String selectionMode = attrs.getAttr(MCV_SELECTION_MODE).isPresent() ?
                attrs.getAttr(MCV_SELECTION_MODE).get().getStringValue() : "single";
            switch (selectionMode) {
                case "none":
                    setSelectionMode(SelectionMode.SELECTION_MODE_NONE);
                    break;
                case "single":
                    setSelectionMode(SelectionMode.SELECTION_MODE_SINGLE);
                    break;
                case "multiple":
                    setSelectionMode(SelectionMode.SELECTION_MODE_MULTIPLE);
                    break;
                default:
                    setSelectionMode(SelectionMode.SELECTION_MODE_RANGE);
                    break;
            }

            final int tileSize = attrs.getAttr(MCV_TILE_SIZE).isPresent() ?
                attrs.getAttr(MCV_TILE_SIZE).get().getDimensionValue() : INVALID_TILE_DIMENSION;
            if (tileSize > INVALID_TILE_DIMENSION) {
                setTileSize(tileSize);
            }

            final int tileWidth = attrs.getAttr(MCV_TILE_WIDTH).isPresent() ?
                attrs.getAttr(MCV_TILE_WIDTH).get().getDimensionValue() : INVALID_TILE_DIMENSION;
            if (tileWidth > INVALID_TILE_DIMENSION) {
                setTileWidth(tileWidth);
            }

            final int tileHeight = attrs.getAttr(MCV_TILE_HEIGHT).isPresent() ?
                attrs.getAttr(MCV_TILE_HEIGHT).get().getDimensionValue() : INVALID_TILE_DIMENSION;
            if (tileHeight > INVALID_TILE_DIMENSION) {
                setTileHeight(tileHeight);
            }
            setSelectionColor(attrs.getAttr(MCV_SELECTION_COLOR).isPresent() ?
                attrs.getAttr(MCV_SELECTION_COLOR).get().getColorValue().getValue() : Color.GRAY.getValue());
            // todo not support
//            CharSequence[] array = a.getTextArray(R.styleable.MaterialCalendarView_mcv_weekDayLabels);
//            if (array != null) {
//                setWeekDayFormatter(new ArrayWeekDayFormatter(array));
//            }
//
//            array = a.getTextArray(R.styleable.MaterialCalendarView_mcv_monthLabels);
//            if (array != null) {
//                setTitleFormatter(new MonthArrayTitleFormatter(array));
//            }
//
//            setHeaderTextAppearance(a.getResourceId(
//                R.styleable.MaterialCalendarView_mcv_headerTextAppearance,
//                R.style.TextAppearance_MaterialCalendarWidget_Header
//            ));
//            setWeekDayTextAppearance(a.getResourceId(
//                R.styleable.MaterialCalendarView_mcv_weekDayTextAppearance,
//                R.style.TextAppearance_MaterialCalendarWidget_WeekDay
//            ));
//            setDateTextAppearance(a.getResourceId(
//                R.styleable.MaterialCalendarView_mcv_dateTextAppearance,
//                R.style.TextAppearance_MaterialCalendarWidget_Date
//            ));
            //noinspection ResourceType
            //      <flag name="none" value="0"/>
            //      <flag name="other_months" value="1"/>
            //      <flag name="out_of_range" value="2"/>
            //      <flag name="decorated_disabled" value="4"/>
            //      <flag name="defaults" value="4"/>
            //      <flag name="all" value="7"/>
            int showOtherDates = attrs.getAttr(MCV_SHOW_OTHER_DATES).isPresent() ?
                attrs.getAttr(MCV_SHOW_OTHER_DATES).get().getIntegerValue() : ShowOtherDates.SHOW_DEFAULTS.value;
            switch (showOtherDates) {
                case 0:
                    setShowOtherDates(ShowOtherDates.SHOW_NONE.value);
                    break;
                case 1:
                    setShowOtherDates(ShowOtherDates.SHOW_OTHER_MONTHS.value);
                    break;
                case 2:
                    setShowOtherDates(ShowOtherDates.SHOW_OUT_OF_RANGE.value);
                    break;
                case 7:
                    setShowOtherDates(ShowOtherDates.SHOW_ALL.value);
                    break;
                default:
                    setShowOtherDates(ShowOtherDates.SHOW_DEFAULTS.value);
                    break;
            }
            setAllowClickDaysOutsideCurrentMonth(!attrs.getAttr(MCV_ALLOW_CLICK_DAYS_OUTSIDE_CURRENT_MONTH).isPresent()
                || attrs.getAttr(MCV_ALLOW_CLICK_DAYS_OUTSIDE_CURRENT_MONTH).get().getBoolValue());
        } catch (Exception e) {
            e.printStackTrace();
        }
        // Adapter is created while parsing the TypedArray attrs, so setup has to happen after
        setupChildren();
        currentMonth = CalendarDay.today();
        setCurrentDate(currentMonth);
//        if (isInEditMode()) {
//            removeView(pager);
//            MonthView monthView = new MonthView(this, currentMonth, getFirstDayOfWeek(), true);
//            monthView.setSelectionColor(getSelectionColor());
//            monthView.setDateTextAppearance(adapter.getDateTextAppearance());
//            monthView.setWeekDayTextAppearance(adapter.getWeekDayTextAppearance());
//            monthView.setShowOtherDates(getShowOtherDates());
//            addView(monthView, new LayoutParams(calendarMode.visibleWeeksCount + DAY_NAMES_ROW));
//        }
    }

    private void setupChildren() {
        addComponent(topbar, new LayoutConfig(MATCH_PARENT, 1));
//        pager.setId(R.id.mcv_pager);
        pager.setPageCacheSize(1);
        int tileHeight = showWeekDays ? calendarMode.visibleWeeksCount + DAY_NAMES_ROW
            : calendarMode.visibleWeeksCount;
        Logger2.d("viewpager 列数=" + tileHeight);
        addComponent(pager, new LayoutConfig(MATCH_PARENT, tileHeight));
    }

    private void updateUi() {
        titleChanger.change(currentMonth);
        enableView(buttonPast, canGoBack());
        enableView(buttonFuture, canGoForward());
        if (sizeWeek != 0) {
            adapter.setWeekDayTextAppearanceSize(sizeWeek);
        }
        if (sizeDay != 0) {
            adapter.setDateTextAppearanceSize(sizeDay);
        }
    }

    /**
     * Change the selection mode of the calendar. The default mode is {
     * #SELECTION_MODE_SINGLE}
     *
     * @param mode the selection mode to change to. This must be one of
     * { #SELECTION_MODE_NONE}, { #SELECTION_MODE_SINGLE},
     * { #SELECTION_MODE_RANGE} or { #SELECTION_MODE_MULTIPLE}.
     * Unknown values will act as { #SELECTION_MODE_SINGLE}
     * #getSelectionMode()
     * #SELECTION_MODE_NONE
     * #SELECTION_MODE_SINGLE
     * #SELECTION_MODE_MULTIPLE
     * #SELECTION_MODE_RANGE
     */
    public void setSelectionMode(final SelectionMode mode) {
        final SelectionMode oldMode = this.selectionMode;
        this.selectionMode = mode;
        switch (mode) {
            case SELECTION_MODE_RANGE:
                clearSelection();
                break;
            case SELECTION_MODE_MULTIPLE:
                break;
            case SELECTION_MODE_SINGLE:
                if (oldMode == SelectionMode.SELECTION_MODE_MULTIPLE || oldMode == SelectionMode.SELECTION_MODE_RANGE) {
                    //We should only have one selection now, so we should pick one
                    List<CalendarDay> dates = getSelectedDates();
                    if (!dates.isEmpty()) {
                        setSelectedDate(getSelectedDate());
                    }
                }
                break;
            default:
            case SELECTION_MODE_NONE:
                this.selectionMode = SelectionMode.SELECTION_MODE_NONE;
                if (oldMode != SelectionMode.SELECTION_MODE_NONE) {
                    //No selection! Clear out!
                    clearSelection();
                }
                break;
        }
        adapter.setSelectionEnabled(selectionMode != SelectionMode.SELECTION_MODE_NONE);
    }

    /**
     * Go to previous month or week without using the button { #buttonPast}. Should only go to
     * previous if { #canGoBack()} is true, meaning it's possible to go to the previous month
     * or week.
     */
    public void goToPrevious() {
        if (canGoBack()) {
            Logger2.w("------->");
            pager.setCurrentPage(pager.getCurrentPage() - 1, true);
        }
    }

    /**
     * Go to next month or week without using the button { #buttonFuture}. Should only go to
     * next if { #canGoForward()} is enabled, meaning it's possible to go to the next month or
     * week.
     */
    public void goToNext() {
        if (canGoForward()) {
            Logger2.w("------->");
            pager.setCurrentPage(pager.getCurrentPage() + 1, true);
        }
    }

    /**
     * Get the current selection mode. The default mode is { #SELECTION_MODE_SINGLE}
     *
     * @return the current selection mode
     * #SELECTION_MODE_NONE
     * #SELECTION_MODE_SINGLE
     * #SELECTION_MODE_MULTIPLE
     * #SELECTION_MODE_RANGE
     */
    public SelectionMode getSelectionMode() {
        return selectionMode;
    }

    /**
     * Use { #getTileWidth()} or { #getTileHeight()} instead. This method is deprecated
     * and will just return the largest of the two sizes.
     *
     * @return tile height or width, whichever is larger
     */
    @Deprecated
    public int getTileSize() {
        return Math.max(tileHeight, tileWidth);
    }

    /**
     * Set the size of each tile that makes up the calendar.
     * Each day is 1 tile, so the widget is 7 tiles wide and 7 or 8 tiles tall
     * depending on the visibility of the { #topbar}.
     *
     * @param size the new size for each tile in pixels
     */
    public void setTileSize(int size) {
        this.tileWidth = size;
        this.tileHeight = size;
        postLayout();
    }

    /**
     * setTileSizeDp
     *
     * @param tileSizeDp the new size for each tile in dips
     * #setTileSize(int)
     */
    public void setTileSizeDp(int tileSizeDp) {
        setTileSize(dpToPx(tileSizeDp));
    }

    /**
     * getTileHeight
     *
     * @return the height of tiles in pixels
     */
    public int getTileHeight() {
        return tileHeight;
    }

    /**
     * Set the height of each tile that makes up the calendar.
     *
     * @param height the new height for each tile in pixels
     */
    public void setTileHeight(int height) {
        this.tileHeight = height;
        postLayout();
    }

    /**
     * setTileHeightDp
     *
     * @param tileHeightDp the new height for each tile in dips
     * #setTileHeight(int)
     */
    public void setTileHeightDp(int tileHeightDp) {
        setTileHeight(dpToPx(tileHeightDp));
    }

    /**
     * getTileWidth
     *
     * @return the width of tiles in pixels
     */
    public int getTileWidth() {
        return tileWidth;
    }

    /**
     * Set the width of each tile that makes up the calendar.
     *
     * @param width the new width for each tile in pixels
     */
    public void setTileWidth(int width) {
        this.tileWidth = width;
        postLayout();
    }

    /**
     * setTileWidthDp
     *
     * @param tileWidthDp the new width for each tile in dips
     * #setTileWidth(int)
     */
    public void setTileWidthDp(int tileWidthDp) {
        setTileWidth(dpToPx(tileWidthDp));
    }

    private int dpToPx(int dp) {
        return AttrHelper.vp2px(dp, getContext());
    }

    /**
     * Whether the pager can page forward, meaning the future month is enabled.
     *
     * @return true if there is a future month that can be shown
     */
    public boolean canGoForward() {
        return pager.getCurrentPage() < (adapter.getCount() - 1);
    }

    /**
     * Whether the pager can page backward, meaning the previous month is enabled.
     *
     * @return true if there is a previous month that can be shown
     */
    public boolean canGoBack() {
        return pager.getCurrentPage() > 0;
    }

//    @Override
//    public boolean onTouchEvent(MotionEvent event) {
//        return pager.dispatchTouchEvent(event);
//    }

    /**
     * getSelectionColor
     *
     * @return the color used for the selection
     */
    public int getSelectionColor() {
        return accentColor;
    }

    /**
     * setSelectionColor
     *
     * @param color The selection color
     */
    public void setSelectionColor(int color) {
        if (color == 0) {
//            if (!isInEditMode()) {
            return;
//            } else {
//                color = Color.GRAY;
//            }
        }
        accentColor = color;
        adapter.setSelectionColor(color);
        invalidate();
    }

    /**
     * Set content description for button past
     *
     * @param description String to use as content description
     */
    public void setContentDescriptionArrowPast(final CharSequence description) {
//        buttonPast.setContentDescription(description);
    }

    /**
     * Set content description for button future
     *
     * @param description String to use as content description
     */
    public void setContentDescriptionArrowFuture(final CharSequence description) {
//        buttonFuture.setContentDescription(description);
    }

    /**
     * Set content description for calendar
     *
     * @param description String to use as content description
     */
    public void setContentDescriptionCalendar(final CharSequence description) {
        calendarContentDescription = description;
    }

    /**
     * Get content description for calendar
     *
     * @return calendar's content description
     */
    public CharSequence getCalendarContentDescription() {
        return calendarContentDescription != null
            ? calendarContentDescription
            : "Calendar";
    }

    /**
     * Set a formatter for day content description.
     *
     * @param formatter the new formatter, null for default
     */
    public void setDayFormatterContentDescription(DayFormatter formatter) {
        adapter.setDayFormatterContentDescription(formatter);
    }

    /**
     * getLeftArrow
     *
     * @return icon used for the left arrow
     */
    public Element getLeftArrow() {
        return buttonPast.getImageElement();
    }

    /**
     * setLeftArrow
     *
     * @param icon the new icon to use for the left paging arrow
     */
    public void setLeftArrow(final Element icon) {
        buttonPast.setImageElement(icon);
    }

    /**
     * getRightArrow
     *
     * @return icon used for the right arrow
     */
    public Element getRightArrow() {
        return buttonFuture.getImageElement();
    }

    /**
     * setRightArrow
     *
     * @param icon the new icon to use for the right paging arrow
     */
    public void setRightArrow(final Element icon) {
        buttonFuture.setImageElement(icon);
        ;
    }

    /**
     * setHeaderTextAppearance
     *
     * @param resourceId The text appearance resource id.
     */
    public void setHeaderTextAppearance(Font resourceId) {
        if (resourceId != null) {
            title.setFont(resourceId);
            title.setTextColor(new Color(0xff000000));
            title.setTextSize(AttrHelper.fp2px(14, getContext()));
        }

    }

    public void setHeaderTextAppearanceSize(int size) {
        title.setTextSize(AttrHelper.vp2px(size, getContext()));
    }

    /**
     * setDateTextAppearance
     *
     * @param font The text appearance resource id.
     */
    public void setDateTextAppearance(Font font) {
        this.font = font;
    }

    public void isBigSize(boolean isBig) {
        this.isBig = isBig;
    }

    public Boolean getBigSize() {
        return isBig;
    }

    public void setDateTextAppearanceSize(int size) {
        this.sizeDay = size;
        this.sizeWeek = size;
        adapter.setDateTextAppearanceSize(size);
    }

    /**
     * setWeekDayTextAppearance
     *
     * @param fontweek The text appearance resource id.
     */
    public void setWeekDayTextAppearance(Font fontweek) {
        if (fontweek != null) {
            this.fontWeek = fontweek;
        }

    }

    public void setWeekDayTextAppearanceSize(int size) {
        adapter.setWeekDayTextAppearanceSize(size);
    }

    /**
     * Get the currently selected date, or null if no selection. Depending on the selection mode,
     * you might get different results.
     *
     * <p>For { #SELECTION_MODE_SINGLE}, returns the selected date.</p>
     * <p>For { #SELECTION_MODE_MULTIPLE}, returns the last date selected.</p>
     * <p>For { #SELECTION_MODE_RANGE}, returns the last date of the range. In most cases, you
     * should probably be using { #getSelectedDates()}.</p>
     * <p>For { #SELECTION_MODE_NONE}, returns null.</p>
     *
     * @return The selected day, or null if no selection. If in multiple selection mode, this
     * will return the last date of the list of selected dates.
     * MaterialCalendarView#getSelectedDates()
     */

    public CalendarDay getSelectedDate() {
        List<CalendarDay> dates = adapter.getSelectedDates();
        if (dates.isEmpty()) {
            return null;
        } else {
            return dates.get(dates.size() - 1);
        }
    }

    /**
     * Return the list of currently selected dates. Mostly useful for { #SELECTION_MODE_MULTIPLE}
     * and { #SELECTION_MODE_RANGE}. For the other modes, check { #getSelectedDate()}.
     *
     * <p>For { #SELECTION_MODE_MULTIPLE}, returns the list in the order of selection.</p>
     * <p>For { #SELECTION_MODE_RANGE}, returns the range of dates ordered chronologically.</p>
     *
     * @return All of the currently selected dates.
     * MaterialCalendarView#getSelectedDate()
     */
    public List<CalendarDay> getSelectedDates() {
        return adapter.getSelectedDates();
    }

    /**
     * Clear the currently selected date(s)
     */
    public void clearSelection() {
        List<CalendarDay> dates = getSelectedDates();
        adapter.clearSelections();
        for (CalendarDay day : dates) {
            dispatchOnDateSelected(day, false);
        }
    }

    /**
     * setSelectedDate
     *
     * @param date a Date set to a day to select. Null to clear selection
     */
    public void setSelectedDate(LocalDate date) {
        setSelectedDate(CalendarDay.from(date));
    }

    /**
     * setSelectedDate
     *
     * @param date a Date to set as selected. Null to clear selection
     */
    public void setSelectedDate(CalendarDay date) {
        clearSelection();
        if (date != null) {
            setDateSelected(date, true);
        }
    }

    /**
     * setDateSelected
     *
     * @param day a CalendarDay to change. Passing null does nothing
     * @param selected true if day should be selected, false to deselect
     */
    public void setDateSelected(CalendarDay day, boolean selected) {
        if (day == null) {
            return;
        }
        adapter.setDateSelected(day, selected);
    }

    /**
     * Get the current first day of the month in month mode, or the first visible day of the
     * currently visible week.
     * <p>
     * For example, in week mode, if the week is July 29th, 2018 to August 4th, 2018,
     * this will return July 29th, 2018. If in month mode and the month is august, then this method
     * will return August 1st, 2018.
     *
     * @return The current month or week shown, will be set to first day of the month in month mode,
     * or the first visible day for a week.
     */
    public CalendarDay getCurrentDate() {
        return adapter.getItem(pager.getCurrentPage());
    }

    /**
     * Set the calendar to a specific month or week based on a date.
     * <p>
     * In month mode, the calendar will be set to the corresponding month.
     * <p>
     * In week mode, the calendar will be set to the corresponding week.
     *
     * @param calendar a Calendar set to a day to focus the calendar on. Null will do nothing
     */
    public void setCurrentDate(LocalDate calendar) {
        setCurrentDate(CalendarDay.from(calendar));
    }

    /**
     * Set the calendar to a specific month or week based on a date.
     * <p>
     * In month mode, the calendar will be set to the corresponding month.
     * <p>
     * In week mode, the calendar will be set to the corresponding week.
     *
     * @param day a CalendarDay to focus the calendar on. Null will do nothing
     */
    public void setCurrentDate(CalendarDay day) {
        setCurrentDate(day, true);
    }

    /**
     * Set the calendar to a specific month or week based on a date.
     * <p>
     * In month mode, the calendar will be set to the corresponding month.
     * <p>
     * In week mode, the calendar will be set to the corresponding week.
     *
     * @param day a CalendarDay to focus the calendar on. Null will do nothing
     * @param useSmoothScroll use smooth scroll when changing months.
     */
    public void setCurrentDate(CalendarDay day, boolean useSmoothScroll) {
        if (day == null) {
            return;
        }
        int index = adapter.getIndexForDay(day);
        pager.setCurrentPage(index, useSmoothScroll);
        Logger2.d("---->" + day);
        updateUi();
    }

    /**
     * getMinimumDate
     *
     * @return the minimum selectable date for the calendar, if any
     */
    public CalendarDay getMinimumDate() {
        return minDate;
    }

    /**
     * getMaximumDate
     *
     * @return the maximum selectable date for the calendar, if any
     */
    public CalendarDay getMaximumDate() {
        return maxDate;
    }

    /**
     * The default value is {#SHOW_DEFAULTS}, which currently is just {
     * #SHOW_DECORATED_DISABLED}.
     * This means that the default visible days are of the current month, in the min-max range.
     *
     * @param showOtherDates flags for showing non-enabled dates
     */
    public void setShowOtherDates(int showOtherDates) {
        adapter.setShowOtherDates(showOtherDates);
    }

    /**
     * Allow the user to click on dates from other months that are not out of range. Go to next or
     * previous month if a day outside the current month is clicked. The day still need to be
     * enabled to be selected.
     * Default value is true. Should be used with {#SHOW_OTHER_MONTHS}.
     *
     * @param enabled True to allow the user to click on a day outside current month displayed
     */
    public void setAllowClickDaysOutsideCurrentMonth(final boolean enabled) {
        this.allowClickDaysOutsideCurrentMonth = enabled;
    }

    /**
     * Set a formatter for weekday labels.
     *
     * @param formatter the new formatter, null for default
     */
    public void setWeekDayFormatter(WeekDayFormatter formatter) {
        adapter.setWeekDayFormatter(formatter == null ? WeekDayFormatter.DEFAULT : formatter);
    }

    /**
     * Set a formatter for day labels.
     *
     * @param formatter the new formatter, null for default
     */
    public void setDayFormatter(DayFormatter formatter) {
        adapter.setDayFormatter(formatter == null ? DayFormatter.DEFAULT : formatter);
    }

    /**
     * Set a { com.prolificinteractive.materialcalendarview.format.WeekDayFormatter}
     * with the provided week day labels
     *
     * @param weekDayLabels Labels to use for the days of the week
     * com.prolificinteractive.materialcalendarview.format.ArrayWeekDayFormatter
     * #setWeekDayFormatter(com.prolificinteractive.materialcalendarview.format.WeekDayFormatter)
     */
    public void setWeekDayLabels(CharSequence[] weekDayLabels) {
        setWeekDayFormatter(new ArrayWeekDayFormatter(weekDayLabels));
    }

    /**
     * Set a { com.prolificinteractive.materialcalendarview.format.WeekDayFormatter}
     * with the provided week day labels
     *
     * @param arrayRes String array resource of week day labels
     * com.prolificinteractive.materialcalendarview.format.ArrayWeekDayFormatter
     * #setWeekDayFormatter(com.prolificinteractive.materialcalendarview.format.WeekDayFormatter)
     */
    public void setWeekDayLabels(int arrayRes) {
        // todo
//        setWeekDayLabels(getResources().getTextArray(arrayRes));
    }

    /**
     * getShowOtherDates
     *
     * @return int of flags used for showing non-enabled dates
     * #SHOW_ALL
     * #SHOW_NONE
     * #SHOW_DEFAULTS
     * #SHOW_OTHER_MONTHS
     * #SHOW_OUT_OF_RANGE
     * #SHOW_DECORATED_DISABLED
     */
    public int getShowOtherDates() {
        return adapter.getShowOtherDates();
    }

    /**
     * allowClickDaysOutsideCurrentMonth
     *
     * @return true if allow click on days outside current month displayed
     */
    public boolean allowClickDaysOutsideCurrentMonth() {
        return allowClickDaysOutsideCurrentMonth;
    }

    /**
     * isShowWeekDays
     *
     * @return true if the week days names are shown
     */
    public boolean isShowWeekDays() {
        return showWeekDays;
    }

    /**
     * Set a custom formatter for the month/year title
     *
     * @param titleFormatter new formatter to use, null to use default formatter
     */
    public void setTitleFormatter(TitleFormatter titleFormatter) {
        titleChanger.setTitleFormatter(titleFormatter);
        adapter.setTitleFormatter(titleFormatter);
        Logger2.d("---->" + titleFormatter);
        updateUi();
    }

    /**
     * Set a { com.prolificinteractive.materialcalendarview.format.TitleFormatter}
     * using the provided month labels
     *
     * @param monthLabels month labels to use
     * com.prolificinteractive.materialcalendarview.format.MonthArrayTitleFormatter
     * #setTitleFormatter(com.prolificinteractive.materialcalendarview.format.TitleFormatter)
     */
    public void setTitleMonths(CharSequence[] monthLabels) {
        setTitleFormatter(new MonthArrayTitleFormatter(monthLabels));
    }

    /**
     * Set a { com.prolificinteractive.materialcalendarview.format.TitleFormatter}
     * using the provided month labels
     *
     * @param arrayRes String array resource of month labels to use
     * com.prolificinteractive.materialcalendarview.format.MonthArrayTitleFormatter
     * #setTitleFormatter(com.prolificinteractive.materialcalendarview.format.TitleFormatter)
     */
    public void setTitleMonths(int arrayRes) {
        // todo
//        setTitleMonths(getResources().getTextArray(arrayRes));
    }

    /**
     * Change the title animation orientation to have a different look and feel.
     *
     * @param orientation { MaterialCalendarView#VERTICAL} or {
     * MaterialCalendarView#HORIZONTAL}
     */
    public void setTitleAnimationOrientation(final int orientation) {
        titleChanger.setOrientation(orientation);
    }

    /**
     * Get the orientation of the animation of the title.
     *
     * @return Title animation orientation { MaterialCalendarView#VERTICAL} or {
     * MaterialCalendarView#HORIZONTAL}
     */
    public int getTitleAnimationOrientation() {
        return titleChanger.getOrientation();
    }

    /**
     * Sets the visibility { #topbar}, which contains
     * the previous month button { #buttonPast}, next month button { #buttonFuture},
     * and the month title { #title}.
     *
     * @param visible Boolean indicating if the topbar is visible
     */
    public void setTopbarVisible(boolean visible) {
        topbar.setVisibility(visible ? Component.VISIBLE : Component.HIDE);
        postLayout();
    }

    /**
     * getTopbarVisible
     *
     * @return true if the topbar is visible
     */
    public boolean getTopbarVisible() {
        return topbar.getVisibility() == Component.VISIBLE;
    }

    /**
     * Get the current { CalendarMode} set of the Calendar.
     *
     * @return Whichever mode the calendar is currently in.
     */
    public CalendarMode getCalendarMode() {
        return calendarMode;
    }

//    @Override
//    protected Parcelable onSaveInstanceState() {
//        SavedState ss = new SavedState(super.onSaveInstanceState());
//        ss.showOtherDates = getShowOtherDates();
//        ss.allowClickDaysOutsideCurrentMonth = allowClickDaysOutsideCurrentMonth();
//        ss.minDate = getMinimumDate();
//        ss.maxDate = getMaximumDate();
//        ss.selectedDates = getSelectedDates();
//        ss.selectionMode = getSelectionMode();
//        ss.topbarVisible = getTopbarVisible();
//        ss.dynamicHeightEnabled = mDynamicHeightEnabled;
//        ss.currentMonth = currentMonth;
//        ss.cacheCurrentPosition = state.cacheCurrentPosition;
//        return ss;
//    }
//
//    @Override
//    protected void onRestoreInstanceState(Parcelable state) {
//        SavedState ss = (SavedState) state;
//        super.onRestoreInstanceState(ss.getSuperState());
//        state().edit()
//            .setMinimumDate(ss.minDate)
//            .setMaximumDate(ss.maxDate)
//            .isCacheCalendarPositionEnabled(ss.cacheCurrentPosition)
//            .commit();
//
//        setShowOtherDates(ss.showOtherDates);
//        setAllowClickDaysOutsideCurrentMonth(ss.allowClickDaysOutsideCurrentMonth);
//        clearSelection();
//        for (CalendarDay calendarDay : ss.selectedDates) {
//            setDateSelected(calendarDay, true);
//        }
//        setTopbarVisible(ss.topbarVisible);
//        setSelectionMode(ss.selectionMode);
//        setDynamicHeightEnabled(ss.dynamicHeightEnabled);
//        setCurrentDate(ss.currentMonth);
//    }
//
//    @Override
//    protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
//        dispatchFreezeSelfOnly(container);
//    }
//
//    @Override
//    protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
//        dispatchThawSelfOnly(container);
//    }

    private void setRangeDates(CalendarDay min, CalendarDay max) {
//        currentMonth = CalendarDay.today();
        CalendarDay c = currentMonth;
        adapter.setRangeDates(min, max);
        currentMonth = c;
        if (min != null) {
            currentMonth = min.isAfter(currentMonth) ? min : currentMonth;
        }
        int position = adapter.getIndexForDay(c);
        Logger2.d("adapter position= " + position + ", count= " + adapter.getCount());//2400, 4801
        pager.setCurrentPage(position, false);
        updateUi();
    }

//    public static class SavedState extends BaseSavedState {
//
//        ShowOtherDates showOtherDates = ShowOtherDates.SHOW_DEFAULTS;
//        boolean allowClickDaysOutsideCurrentMonth = true;
//        CalendarDay minDate = null;
//        CalendarDay maxDate = null;
//        List<CalendarDay> selectedDates = new ArrayList<>();
//        boolean topbarVisible = true;
//        int selectionMode = SELECTION_MODE_SINGLE;
//        boolean dynamicHeightEnabled = false;
//        CalendarDay currentMonth = null;
//        boolean cacheCurrentPosition;
//
//        SavedState(Parcelable superState) {
//            super(superState);
//        }
//
//        @Override
//        public void writeToParcel(Parcel out, int flags) {
//            super.writeToParcel(out, flags);
//            out.writeInt(showOtherDates);
//            out.writeByte((byte) (allowClickDaysOutsideCurrentMonth ? 1 : 0));
//            out.writeParcelable(minDate, 0);
//            out.writeParcelable(maxDate, 0);
//            out.writeTypedList(selectedDates);
//            out.writeInt(topbarVisible ? 1 : 0);
//            out.writeInt(selectionMode);
//            out.writeInt(dynamicHeightEnabled ? 1 : 0);
//            out.writeParcelable(currentMonth, 0);
//            out.writeByte((byte) (cacheCurrentPosition ? 1 : 0));
//        }
//
//        public static final Parcelable.Creator<SavedState> CREATOR
//            = new Parcelable.Creator<SavedState>() {
//            public SavedState createFromParcel(Parcel in) {
//                return new SavedState(in);
//            }
//
//            public SavedState[] newArray(int size) {
//                return new SavedState[size];
//            }
//        };
//
//        private SavedState(Parcel in) {
//            super(in);
//            showOtherDates = in.readInt();
//            allowClickDaysOutsideCurrentMonth = in.readByte() != 0;
//            ClassLoader loader = CalendarDay.class.getClassLoader();
//            minDate = in.readParcelable(loader);
//            maxDate = in.readParcelable(loader);
//            in.readTypedList(selectedDates, CalendarDay.CREATOR);
//            topbarVisible = in.readInt() == 1;
//            selectionMode = in.readInt();
//            dynamicHeightEnabled = in.readInt() == 1;
//            currentMonth = in.readParcelable(loader);
//            cacheCurrentPosition = in.readByte() != 0;
//        }
//    }
//
//    private static int getThemeAccentColor(Context context) {
//        int colorAttr;
//        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
//            colorAttr = android.R.attr.colorAccent;
//        } else {
//            //Get colorAccent defined for AppCompat
//            colorAttr =
//                context.getResources().getIdentifier("colorAccent", "attr", context.getPackageName());
//        }
//        TypedValue outValue = new TypedValue();
//        context.getTheme().resolveAttribute(colorAttr, outValue, true);
//        return outValue.data;
//    }

    /**
     * getFirstDayOfWeek
     *
     * @return The first day of the week as a { Calendar} day constant.
     */
    public DayOfWeek getFirstDayOfWeek() {
        return firstDayOfWeek;
    }

    /**
     * By default, the calendar will take up all the space needed to show any month (6 rows).
     * By enabling dynamic height, the view will change height dependant on the visible month.
     * <p>
     * This means months that only need 5 or 4 rows to show the entire month will only take up
     * that many rows, and will grow and shrink as necessary.
     *
     * @param useDynamicHeight true to have the view different heights based on the visible month
     */
    public void setDynamicHeightEnabled(boolean useDynamicHeight) {
        this.mDynamicHeightEnabled = useDynamicHeight;
    }

    /**
     * isDynamicHeightEnabled
     *
     * @return the dynamic height state - true if enabled.
     */
    public boolean isDynamicHeightEnabled() {
        return mDynamicHeightEnabled;
    }

    /**
     * Add a collection of day decorators
     *
     * @param decorators decorators to add
     * @param font font
     */
    public void addDecorators(Collection<? extends DayViewDecorator> decorators, Font font) {
        if (decorators == null) {
            return;
        }
        dayViewDecorators.addAll(decorators);
        adapter.setDecorators(dayViewDecorators, font);
    }

    /**
     * Add several day decorators
     *
     * @param decorators decorators to add
     */
    public void addDecorators(DayViewDecorator... decorators) {
        addDecorators(Arrays.asList(decorators), font);
    }

    /**
     * Add a day decorator
     *
     * @param decorator decorator to add
     */
    public void addDecorator(DayViewDecorator decorator) {
        if (decorator == null) {
            return;
        }
        dayViewDecorators.add(decorator);
        adapter.setDecorators(dayViewDecorators, font);
    }

    /**
     * Remove all decorators
     */
    public void removeDecorators() {
        dayViewDecorators.clear();
        adapter.setDecorators(dayViewDecorators, font);
    }

    /**
     * Remove a specific decorator instance. Same rules as { List#remove(Object)}
     *
     * @param decorator decorator to remove
     */
    public void removeDecorator(DayViewDecorator decorator) {
        dayViewDecorators.remove(decorator);
        adapter.setDecorators(dayViewDecorators, font);
    }

    /**
     * Invalidate decorators after one has changed internally. That is, if a decorator mutates, you
     * should call this method to update the widget.
     */
    public void invalidateDecorators() {
        adapter.invalidateDecorators(font);
    }


    /*
     * Listener/Callback Code
     */

    /**
     * Sets the listener to be notified upon selected date changes.
     *
     * @param listener thing to be notified
     */
    public void setOnDateChangedListener(OnDateSelectedListener listener) {
        this.listener = listener;
    }

    /**
     * Sets the listener to be notified upon long clicks on dates.
     *
     * @param longClickListener thing to be notified
     */
    public void setOnDateLongClickListener(OnDateLongClickListener longClickListener) {
        this.longClickListener = longClickListener;
    }

    /**
     * Sets the listener to be notified upon month changes.
     *
     * @param listener thing to be notified
     */
    public void setOnMonthChangedListener(OnMonthChangedListener listener) {
        this.monthListener = listener;
    }

    /**
     * Sets the listener to be notified upon a range has been selected.
     *
     * @param listener thing to be notified
     */
    public void setOnRangeSelectedListener(OnRangeSelectedListener listener) {
        this.rangeListener = listener;
    }

    /**
     * Add listener to the title or null to remove it.
     *
     * @param listener Listener to be notified.
     */
    public void setOnTitleClickListener(final ClickedListener listener) {
        title.setClickedListener(listener);
    }

    /**
     * Dispatch date change events to a listener, if set
     *
     * @param day the day that was selected
     * @param selected true if the day is now currently selected, false otherwise
     */
    protected void dispatchOnDateSelected(final CalendarDay day, final boolean selected) {
        if (listener != null) {
            listener.onDateSelected(MaterialCalendarView.this, day, selected);
        }
    }

    /**
     * Dispatch a range of days to a range listener, if set, ordered chronologically.
     *
     * @param days Enclosing days ordered from first to last day.
     */
    protected void dispatchOnRangeSelected(final List<CalendarDay> days) {
        if (rangeListener != null) {
            rangeListener.onRangeSelected(MaterialCalendarView.this, days);
        }
    }

    /**
     * Dispatch date change events to a listener, if set
     *
     * @param day first day of the new month
     */
    protected void dispatchOnMonthChanged(final CalendarDay day) {
        if (monthListener != null) {
            monthListener.onMonthChanged(MaterialCalendarView.this, day);
        }
    }

    /**
     * Call by { CalendarPagerView} to indicate that a day was clicked and we should handle it.
     * This method will always process the click to the selected date.
     *
     * @param date date of the day that was clicked
     * @param nowSelected true if the date is now selected, false otherwise
     */
    protected void onDateClicked(CalendarDay date, boolean nowSelected) {
        switch (selectionMode) {
            case SELECTION_MODE_MULTIPLE: {
                adapter.setDateSelected(date, nowSelected);
                dispatchOnDateSelected(date, nowSelected);
            }
            break;
            case SELECTION_MODE_RANGE: {
                final List<CalendarDay> currentSelection = adapter.getSelectedDates();

                if (currentSelection.size() == 0) {
                    // Selecting the first date of a range
                    adapter.setDateSelected(date, nowSelected);
                    dispatchOnDateSelected(date, nowSelected);
                } else if (currentSelection.size() == 1) {
                    // Selecting the second date of a range
                    final CalendarDay firstDaySelected = currentSelection.get(0);
                    if (firstDaySelected.equals(date)) {
                        // Right now, we are not supporting a range of one day, so we are removing the day instead.
                        adapter.setDateSelected(date, nowSelected);
                        dispatchOnDateSelected(date, nowSelected);
                    } else if (firstDaySelected.isAfter(date)) {
                        // Selecting a range, dispatching in reverse order...
                        adapter.selectRange(date, firstDaySelected);
                        dispatchOnRangeSelected(adapter.getSelectedDates());
                    } else {
                        // Selecting a range, dispatching in order...
                        adapter.selectRange(firstDaySelected, date);
                        dispatchOnRangeSelected(adapter.getSelectedDates());
                    }
                } else {
                    // Clearing selection and making a selection of the new date.
                    adapter.clearSelections();
                    adapter.setDateSelected(date, nowSelected);
                    dispatchOnDateSelected(date, nowSelected);
                }
            }
            break;
            default:
            case SELECTION_MODE_SINGLE: {
                adapter.clearSelections();
                adapter.setDateSelected(date, true);
                dispatchOnDateSelected(date, true);
            }
            break;
        }
    }

    /**
     * Select a fresh range of date including first day and last day.
     *
     * @param firstDay first day of the range to select
     * @param lastDay last day of the range to select
     */
    public void selectRange(final CalendarDay firstDay, final CalendarDay lastDay) {
        if (firstDay == null || lastDay == null) {
            return;
        } else if (firstDay.isAfter(lastDay)) {
            adapter.selectRange(lastDay, firstDay);
            dispatchOnRangeSelected(adapter.getSelectedDates());
        } else {
            adapter.selectRange(firstDay, lastDay);
            dispatchOnRangeSelected(adapter.getSelectedDates());
        }
    }

    /**
     * Call by { CalendarPagerView} to indicate that a day was clicked and we should handle it
     *
     * @param dayView dayView
     */
    protected void onDateClicked(final DayView dayView) {
        final CalendarDay currentDate = getCurrentDate();
        final CalendarDay selectedDate = dayView.getDate();
        final int currentMonth = currentDate.getMonth();
        final int selectedMonth = selectedDate.getMonth();

        if (calendarMode == CalendarMode.MONTHS
            && allowClickDaysOutsideCurrentMonth
            && currentMonth != selectedMonth) {
            if (currentDate.isAfter(selectedDate)) {
                goToPrevious();
            } else if (currentDate.isBefore(selectedDate)) {
                goToNext();
            }
        }
        onDateClicked(dayView.getDate(), !dayView.isChecked());
    }

    /**
     * Call by { CalendarPagerView} to indicate that a day was long clicked and we should handle
     * it
     *
     * @param dayView dayView
     */
    protected void onDateLongClicked(final DayView dayView) {
        if (longClickListener != null) {
            longClickListener.onDateLongClick(MaterialCalendarView.this, dayView.getDate());
        }
    }

    /**
     * Called by the adapter for cases when changes in state result in dates being unselected
     *
     * @param date date that should be de-selected
     */
    protected void onDateUnselected(CalendarDay date) {
        dispatchOnDateSelected(date, false);
    }

    /*
     * Show Other Dates Utils
     */

    /**
     * showOtherMonths
     *
     * @param showOtherDates int flag for show other dates
     * @return true if the other months flag is set
     */
    public static boolean showOtherMonths(int showOtherDates) {
        return (showOtherDates & ShowOtherDates.SHOW_OTHER_MONTHS.value) != 0;
    }

    /**
     * showOutOfRange
     *
     * @param showOtherDates int flag for show other dates
     * @return true if the out of range flag is set
     */
    public static boolean showOutOfRange(int showOtherDates) {
        return (showOtherDates & ShowOtherDates.SHOW_OUT_OF_RANGE.value) != 0;
    }

    /**
     * showDecoratedDisabled
     *
     * @param showOtherDates int flag for show other dates
     * @return true if the decorated disabled flag is set
     */
    public static boolean showDecoratedDisabled(int showOtherDates) {
        return (showOtherDates & ShowOtherDates.SHOW_DECORATED_DISABLED.value) != 0;
    }

    @Override
    public boolean onEstimateSize(final int widthMeasureSpec, final int heightMeasureSpec) {
        final int specWidthSize = EstimateSpec.getSize(widthMeasureSpec);
        final int specWidthMode = EstimateSpec.getMode(widthMeasureSpec);
        final int specHeightSize = EstimateSpec.getSize(heightMeasureSpec);
        final int specHeightMode = EstimateSpec.getMode(heightMeasureSpec);

        //We need to disregard padding for a while. This will be added back later
        final int desiredWidth = specWidthSize - getPaddingLeft() - getPaddingRight();
        final int desiredHeight = specHeightSize - getPaddingTop() - getPaddingBottom();

        final int weekCount = getWeekCountBasedOnMode();

        final int viewTileHeight = getTopbarVisible() ? (weekCount + 1) : weekCount;
        //Calculate independent tile sizes for later
        int desiredTileWidth = desiredWidth / DEFAULT_DAYS_IN_WEEK;
        int desiredTileHeight = desiredHeight / viewTileHeight;
        int measureTileSize = -1;
        int measureTileWidth = -1;
        int measureTileHeight = -1;
        if (this.tileWidth != INVALID_TILE_DIMENSION || this.tileHeight != INVALID_TILE_DIMENSION) {
            if (this.tileWidth > 0) {
                //We have a tileWidth set, we should use that
                measureTileWidth = this.tileWidth;
            } else {
                measureTileWidth = desiredTileWidth;
            }
            if (this.tileHeight > 0) {
                //We have a tileHeight set, we should use that
                measureTileHeight = this.tileHeight;
            } else {
                measureTileHeight = desiredTileHeight;
            }
        } else if (specWidthMode == EstimateSpec.PRECISE || specWidthMode == EstimateSpec.NOT_EXCEED) {
            if (specHeightMode == EstimateSpec.PRECISE) {
                //Pick the smaller of the two explicit sizes
                measureTileSize = Math.min(desiredTileWidth, desiredTileHeight);
            } else {
                //Be the width size the user wants
                measureTileSize = desiredTileWidth;
            }
        } else if (specHeightMode == EstimateSpec.PRECISE || specHeightMode == EstimateSpec.NOT_EXCEED) {
            //Be the height size the user wants
            measureTileSize = desiredTileHeight;
        }

        if (measureTileSize > 0) {
            //Use measureTileSize if set
            measureTileHeight = measureTileSize;
            measureTileWidth = measureTileSize;
        } else if (measureTileSize <= 0) {
            if (measureTileWidth <= 0) {
                //Set width to default if no value were set
                measureTileWidth = dpToPx(DEFAULT_TILE_SIZE_DP);
            }
            if (measureTileHeight <= 0) {
                //Set height to default if no value were set
                measureTileHeight = dpToPx(DEFAULT_TILE_SIZE_DP);
            }
        }

        //Calculate our size based off our measured tile size
        int measuredWidth = measureTileWidth * DEFAULT_DAYS_IN_WEEK;
        int measuredHeight = measureTileHeight * viewTileHeight;

        //Put padding back in from when we took it away
        measuredWidth += getPaddingLeft() + getPaddingRight();
        measuredHeight += getPaddingTop() + getPaddingBottom();
        //Contract fulfilled, setting out measurements
        setEstimatedSize(
            //We clamp inline because we want to use un-clamped versions on the children
            clampSize(measuredWidth, widthMeasureSpec),
            clampSize(measuredHeight, heightMeasureSpec)
        );
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            final Component child = getComponentAt(i);
            LayoutConfig lc = child.getLayoutConfig();
            int childWidthMeasureSpec = EstimateSpec.getSizeWithMode(
                DEFAULT_DAYS_IN_WEEK * measureTileWidth, EstimateSpec.PRECISE
            );
            int childHeightMeasureSpec = EstimateSpec.getSizeWithMode(
                lc.height * measureTileHeight, EstimateSpec.PRECISE
            );
            child.estimateSize(childWidthMeasureSpec, childHeightMeasureSpec);
        }
        return true;
    }


    private int getWeekCountBasedOnMode() {
        int weekCount = calendarMode.visibleWeeksCount;
        final boolean isInMonthsMode = calendarMode.equals(CalendarMode.MONTHS);
        if (isInMonthsMode && mDynamicHeightEnabled && adapter != null && pager != null) {
            final LocalDate cal = adapter.getItem(pager.getCurrentPage()).getDate();
            final LocalDate tempLastDay = cal.withDayOfMonth(cal.lengthOfMonth());
            weekCount = tempLastDay.get(WeekFields.of(firstDayOfWeek, 1).weekOfMonth());
        }
        return showWeekDays ? weekCount + DAY_NAMES_ROW : weekCount;
    }

    /**
     * Clamp the size to the measure spec.
     *
     * @param size Size we want to be
     * @param spec Measure spec to clamp against
     * @return the appropriate size to pass to  View#setMeasuredDimension(int, int)
     */
    private static int clampSize(int size, int spec) {
        int specMode = EstimateSpec.getMode(spec);
        int specSize = EstimateSpec.getSize(spec);
        switch (specMode) {
            case EstimateSpec.PRECISE: {
                return EstimateSpec.getSizeWithMode(specSize, EstimateSpec.PRECISE);
            }
            case EstimateSpec.NOT_EXCEED: {
                return EstimateSpec.getSizeWithMode(Math.min(size, specSize), EstimateSpec.PRECISE);
            }
            case EstimateSpec.UNCONSTRAINT:
            default: {
                return EstimateSpec.getSizeWithMode(size, EstimateSpec.PRECISE);
            }
        }
    }

    @Override
    public boolean onArrange(int left, int top, int w, int h) {
        final int count = getChildCount();
        final int parentLeft = getPaddingLeft();
        final int parentWidth = getRight() - getLeft() - parentLeft - getPaddingRight();
        int childTop = getPaddingTop();
        for (int i = 0; i < count; i++) {
            final Component child = getComponentAt(i);
            if (child.getVisibility() == Component.HIDE) {
                continue;
            }
            final int width = child.getEstimatedWidth();
            final int height = child.getEstimatedHeight();
            int delta = (parentWidth - width) / 2;
            int childLeft = parentLeft + delta;
            child.arrange(childLeft, childTop, width, height);
            childTop += height;
        }
        return true;
    }

    /**
     * Enable or disable the ability to swipe between months.
     *
     * @param pagingEnabled pass false to disable paging, true to enable (default)
     */
    public void setPagingEnabled(boolean pagingEnabled) {
        Logger2.d("---->" + pagingEnabled);
        pager.setPagingEnabled(pagingEnabled);
        updateUi();
    }

    /**
     * isPagingEnabled
     *
     * @return true if swiping months is enabled, false if disabled. Default is true.
     */
    public boolean isPagingEnabled() {
        return pager.isPagingEnabled();
    }

    /**
     * Preserve the current parameters of the Material Calendar View.
     *
     * @return return
     */
    public State state() {
        return state;
    }

    /**
     * Initialize the parameters from scratch.
     *
     * @return return
     */
    public StateBuilder newState() {
        return new StateBuilder();
    }

    public class State {
        private final CalendarMode calendarMode;
        private final DayOfWeek firstDayOfWeek;
        private final CalendarDay minDate;
        private final CalendarDay maxDate;
        private final boolean cacheCurrentPosition;
        private final boolean showWeekDays;

        private State(final StateBuilder builder) {
            calendarMode = builder.calendarMode;
            firstDayOfWeek = builder.firstDayOfWeek;
            minDate = builder.minDate;
            maxDate = builder.maxDate;
            cacheCurrentPosition = builder.cacheCurrentPosition;
            showWeekDays = builder.showWeekDays;
        }

        /**
         * Modify parameters from current state.
         *
         * @return return
         */
        public StateBuilder edit() {
            return new StateBuilder(this);
        }
    }

    public class StateBuilder {
        private CalendarMode calendarMode;
        private DayOfWeek firstDayOfWeek;
        private boolean cacheCurrentPosition = false;
        private CalendarDay minDate = null;
        private CalendarDay maxDate = null;
        private boolean showWeekDays;

        public StateBuilder() {
            calendarMode = CalendarMode.MONTHS;
            firstDayOfWeek = LocalDate.now().with(WeekFields.of(Locale.getDefault()).dayOfWeek(), 1).getDayOfWeek();
        }

        private StateBuilder(final State state) {
            calendarMode = state.calendarMode;
            firstDayOfWeek = state.firstDayOfWeek;
            minDate = state.minDate;
            maxDate = state.maxDate;
            cacheCurrentPosition = state.cacheCurrentPosition;
            showWeekDays = state.showWeekDays;
        }

        /**
         * Sets the first day of the week.
         * <p>
         * Uses the { DayOfWeek} day constants.
         *
         * @param day The first day of the week as a { DayOfWeek} enum.
         * @return return
         */
        public StateBuilder setFirstDayOfWeek(DayOfWeek day) {
            this.firstDayOfWeek = day;
            return this;
        }

        /**
         * Set calendar display mode. The default mode is Months.
         * When switching between modes will select todays date, or the selected date,
         * if selection mode is single.
         *
         * @param mode - calendar mode
         * @return return
         */
        public StateBuilder setCalendarDisplayMode(CalendarMode mode) {
            this.calendarMode = mode;
            return this;
        }

        /**
         * setMinimumDate
         *
         * @param date set the minimum selectable date, null for no minimum
         * @return return
         */
        public StateBuilder setMinimumDate(LocalDate date) {
            setMinimumDate(CalendarDay.from(date));
            return this;
        }

        /**
         * setMinimumDate
         *
         * @param calendar calendar
         * @return return
         */
        public StateBuilder setMinimumDate(CalendarDay calendar) {
            minDate = calendar;
            return this;
        }

        /**
         * setMaximumDate
         *
         * @param date set the maximum selectable date, null for no maximum
         * @return return
         */
        public StateBuilder setMaximumDate(LocalDate date) {
            setMaximumDate(CalendarDay.from(date));
            return this;
        }

        /**
         * setMaximumDate
         *
         * @param calendar set the maximum selectable date, null for no maximum
         * @return return
         */
        public StateBuilder setMaximumDate(CalendarDay calendar) {
            maxDate = calendar;
            return this;
        }

        /**
         * setShowWeekDays
         *
         * @param showWeekDays true to show week days names
         * @return return
         */
        public StateBuilder setShowWeekDays(boolean showWeekDays) {
            this.showWeekDays = showWeekDays;
            return this;
        }

        /**
         * Use this method to enable saving the current position when switching
         * between week and month mode. By default, the calendar update to the latest selected date
         * or the current date. When set to true, the view will used the month that the calendar is
         * currently on.
         *
         * @param cacheCurrentPosition Set to true to cache the current position, false otherwise.
         * @return return
         */
        public StateBuilder isCacheCalendarPositionEnabled(final boolean cacheCurrentPosition) {
            this.cacheCurrentPosition = cacheCurrentPosition;
            return this;
        }

        public void commit() {
            MaterialCalendarView.this.commit(new State(this));
        }

    }


    private void commit(State state) {
        // Use the calendarDayToShow to determine which date to focus on for the case of switching between month and week views
        CalendarDay calendarDayToShow = null;
        if (adapter != null && state.cacheCurrentPosition) {
            calendarDayToShow = adapter.getItem(pager.getCurrentPage());
            if (calendarMode != state.calendarMode) {
                CalendarDay currentlySelectedDate = getSelectedDate();
                if (calendarMode == CalendarMode.MONTHS && currentlySelectedDate != null) {
                    // Going from months to weeks
                    LocalDate lastVisibleCalendar = calendarDayToShow.getDate();
                    CalendarDay lastVisibleCalendarDay = CalendarDay.from(lastVisibleCalendar.plusDays(1));
                    if (currentlySelectedDate.equals(calendarDayToShow) ||
                        (currentlySelectedDate.isAfter(calendarDayToShow) && currentlySelectedDate.isBefore(
                            lastVisibleCalendarDay))) {
                        // Currently selected date is within view, so center on that
                        calendarDayToShow = currentlySelectedDate;
                    }
                } else if (calendarMode == CalendarMode.WEEKS) {
                    // Going from weeks to months
                    LocalDate lastVisibleCalendar = calendarDayToShow.getDate();
                    CalendarDay lastVisibleCalendarDay = CalendarDay.from(lastVisibleCalendar.plusDays(6));
                    if (currentlySelectedDate != null &&
                        (currentlySelectedDate.equals(calendarDayToShow) || currentlySelectedDate.equals(
                            lastVisibleCalendarDay) ||
                            (currentlySelectedDate.isAfter(calendarDayToShow)
                                && currentlySelectedDate.isBefore(lastVisibleCalendarDay)))) {
                        // Currently selected date is within view, so center on that
                        calendarDayToShow = currentlySelectedDate;
                    } else {
                        calendarDayToShow = lastVisibleCalendarDay;
                    }
                }
            }
        }
        this.state = state;
        // Save states parameters
        calendarMode = state.calendarMode;
        firstDayOfWeek = state.firstDayOfWeek;
        minDate = state.minDate;
        maxDate = state.maxDate;
        showWeekDays = state.showWeekDays;
        // Recreate adapter
        final CalendarPagerAdapter<?> newAdapter;
        Logger2.d("calendarMode=" + calendarMode + ", firstDayOfWeek=" + firstDayOfWeek + ", showWeekDays=" + showWeekDays);
        switch (calendarMode) {
            case MONTHS:
                newAdapter = new MonthPagerAdapter(this, font, fontWeek);
                break;
            case WEEKS:
                newAdapter = new WeekPagerAdapter(this, font, fontWeek);
                break;
            default:
                throw new IllegalArgumentException("Provided display mode which is not yet implemented");
        }
        if (adapter == null) {
            adapter = newAdapter;
        } else {
            adapter = adapter.migrateStateAndReturn(newAdapter);
        }
        adapter.setShowWeekDays(showWeekDays);
        pager.setProvider(adapter);

        setRangeDates(minDate, maxDate);

        // Reset height params after mode change
        int tileHeight = showWeekDays ? calendarMode.visibleWeeksCount + DAY_NAMES_ROW
            : calendarMode.visibleWeeksCount;
        pager.setLayoutConfig(new LayoutConfig(MATCH_PARENT, tileHeight));

        setCurrentDate(
            selectionMode == SelectionMode.SELECTION_MODE_SINGLE && !adapter.getSelectedDates().isEmpty()
                ? adapter.getSelectedDates().get(0)
                : CalendarDay.today());

        if (calendarDayToShow != null) {
            pager.setCurrentPage(adapter.getIndexForDay(calendarDayToShow));
        }

        invalidateDecorators();
        updateUi();
    }

    /**
     * Used for enabling or disabling views, while also changing the alpha.
     *
     * @param view The view to enable or disable.
     * @param enable Whether to enable or disable the view.
     */
    private static void enableView(final Component view, final boolean enable) {
        view.setEnabled(enable);
        view.setAlpha(enable ? 1f : 0.1f);
    }
    public boolean isOldStyle() {
        return oldStyle;
    }

    public void setOldStyle(boolean oldStyle) {
        this.oldStyle = oldStyle;
    }
}
